import hashlib
import json
import os
import sys
import asyncio
from collections import OrderedDict

import tornado.web
from tornado import gen
from tornado.httpclient import AsyncHTTPClient
import datetime
import urllib

sys.path.append(os.path.abspath('../'))
from baseHandler import init_logger, Application, PayHandler, http_result

log_path = '/data/log/huiju/huiju.log'
if not os.path.exists(os.path.dirname(log_path)):
    os.makedirs(os.path.dirname(log_path))
logger = init_logger("huiju", log_path, screen=False)


def byte2hex(bins):
    return ''.join(["{0:02x}".format(x) for x in bins])


def hex_byte(hexStr):
    return bytes.fromhex(hexStr)


template = template = """<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Please Wait...</title>
</head>
<body onload="OnLoadSubmit();">
<form id="gatewayform" action="{url}" method="post">
     <input type="hidden" name="p1_MerchantNo" id="p1_MerchantNo" value='{p1_MerchantNo}'/>
     <input type="hidden" name="p2_OrderNo", id="p2_OrderNo", value="{p2_OrderNo}">
     <input type="hidden" name="p3_Amount", id="p3_Amount", value="{p3_Amount}">
     <input type="hidden" name="p4_Cur" id="p4_Cur" value="{p4_Cur}"/>
     <input type="hidden" name="p5_ProductName" id="p5_ProductName" value="{p5_ProductName}"/>
    <input type="hidden" name="p8_NotifyUrl" id="p8_NotifyUrl" value="{p8_NotifyUrl}"/>
    <input type="hidden" name="p9_FrpCode" id="p9_FrpCode" value="{p9_FrpCode}"/>
    <input type="hidden" name="p7_ReturnUrl" id="p7_ReturnUrl" value="{p7_ReturnUrl}"/>
    <input type="hidden" name="pa_OrderPeriod" id="pa_OrderPeriod" value="{pa_OrderPeriod}"/>
    <input type="hidden" name="hmac" id="hmac" value="{hmac}"/>
</form>
<script type="text/javascript">
    function OnLoadSubmit()
    {{
        document.getElementById("gatewayform").submit();
    }}
</script>
</body>
</html>
"""


class AliPay:
    notify_url = 'http://150.109.62.163:8000/noti/huiju'
    settle_noti = 'http://150.109.62.163:8000/noti/huiju_sett'
    ali_noti = 'http://150.109.62.163:8000/noti/huiju_qr'

    def __init__(self, url, merNo, key):
        self.url = url
        self.mer_code = merNo
        self.key = key

    def quick_pay(self, request_obj):
        data_obj = {
            "p1_MerchantNo": self.mer_code,
            "p2_OrderNo": request_obj["OUTERORDERNO"],
            "p3_Amount": request_obj.get("OPMONEY", ""),
            "p4_Cur": "1",
            "p5_ProductName": request_obj.get("body", ""),
            "p8_NotifyUrl": self.notify_url,
            "p9_FrpCode": "NOCARD_C",
            "q1_BankCard": request_obj.get("usercardno", ""),
            "q5_TransactionModel": "MODEL1"
        }
        pay_md5sign = "".join([str(v) for k, v in sorted(data_obj.items()) if v])
        pay_md5sign += self.key
        logger.info(pay_md5sign)
        sign = hashlib.md5(pay_md5sign.encode()).hexdigest()
        data_obj['hmac'] = sign
        return data_obj

    def gateway(self, request_obj):
        data_obj = {
            "p1_MerchantNo": self.mer_code,
            "p2_OrderNo": request_obj["OUTERORDERNO"],
            "p3_Amount": request_obj.get("OPMONEY", ""),
            "p4_Cur": "1",
            "p5_ProductName": request_obj.get("body", ""),
            "p8_NotifyUrl": self.notify_url,
            "p7_ReturnUrl": request_obj.get("returnurl", "") ,
            "p9_FrpCode": request_obj.get("bankabbr") + '_NET_B2C',
            "pa_OrderPeriod": "0"
        }
        pay_md5sign = "".join([str(v) for k, v in sorted(data_obj.items()) if v])
        pay_md5sign += self.key
        logger.info(pay_md5sign)
        sign = hashlib.md5(pay_md5sign.encode()).hexdigest()
        data_obj['hmac'] = sign
        return data_obj

    def query(self, request_obj):
        data_obj = {
            "p1_MerchantNo": self.mer_code,
            "p2_OrderNo": request_obj["OUTERORDERNO"],
        }
        pay_md5sign = "".join([str(v) for k, v in sorted(data_obj.items())])
        pay_md5sign += self.key
        logger.info(pay_md5sign)
        sign = hashlib.md5(pay_md5sign.encode()).hexdigest()
        data_obj['hmac'] = sign
        return data_obj

    def c2b(self, request_obj):
        data_obj = {
            "p0_Version": "1.0",
            "p1_MerchantNo": self.mer_code,
            "p2_OrderNo": request_obj["OUTERORDERNO"],
            "p3_Amount": request_obj.get("OPMONEY", ""),
            "p4_Cur": "1",
            "p5_ProductName": request_obj.get("BODY", ""),
            "p9_NotifyUrl": self.ali_noti,
            # "p9_FrpCode": "NOCARD_C",
            "q1_FrpCode": "ALIPAY_NATIVE",
        }
        pay_md5sign = "".join([str(v) for k, v in sorted(data_obj.items())])
        pay_md5sign += self.key
        logger.info(pay_md5sign)
        sign = hashlib.md5(pay_md5sign.encode()).hexdigest()
        data_obj['hmac'] = sign
        return data_obj

    def settle(self, request_obj):
        request_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        data_obj = [
            ("userNo", self.mer_code),
            ("productCode", request_obj.get("settle_type", "BANK_PAY_MAT_ENDOWMENT_ORDER")),
            ("requestTime", request_time),
            ("merchantOrderNo", request_obj["OUTERORDERNO"]),
            ("receiverAccountNoEnc", request_obj['usercardno']),
            ("receiverNameEnc", request_obj['username']),
            ("receiverAccountType", request_obj.get("accounttype") or "201"),
            ("receiverBankChannelNo", request_obj.get('bankunion', "")),
            ("paidAmount", request_obj['amount']),
            ('currency', "201"),
            ('isChecked', '202'),
            ('paidDesc', request_obj.get('body')),
            ('paidUse', '204'),
            ('callbackUrl', self.settle_noti)
        ]
        pay_md5sign = "".join([str(v) for k, v in data_obj if v])
        pay_md5sign += self.key
        logger.info(pay_md5sign)
        sign = hashlib.md5(pay_md5sign.encode()).hexdigest()
        # data_obj = dict(data_obj)
        data_obj.append(('hmac', sign))
        res = OrderedDict()
        for k, v in data_obj:
            res[k] = v
        return res

    def query_sett(self, request_obj):
        data_obj = [
            ("userNo", self.mer_code),
            ("merchantOrderNo", request_obj["OUTERORDERNO"]),
        ]
        pay_md5sign = "".join([str(v) for k, v in data_obj if v])
        pay_md5sign += self.key
        logger.info(pay_md5sign)
        sign = hashlib.md5(pay_md5sign.encode()).hexdigest()
        # data_obj = dict(data_obj)
        data_obj.append(('hmac', sign))
        res = OrderedDict()
        for k, v in data_obj:
            res[k] = v
        return res

class AlipayHandler(tornado.web.RequestHandler):
    async def post(self, method):
        http_client = AsyncHTTPClient()
        logger.info(self.request.body)
        request_obj = json.loads(self.request.body.decode('utf-8'))
        merno = request_obj['CFGPARA']['MERNO']
        appkey = request_obj['CFGPARA']['KEY']
        alipay = AliPay("https://www.joinpay.com", merno, appkey)

        if 'OPMONEY' in request_obj:
            request_obj['OPMONEY'] = int(request_obj['OPMONEY']) / 100
            request_obj['OPMONEY'] = '%.2f' % request_obj['OPMONEY']
        if method == 'alipayc2b':
            url = alipay.url + '/trade/uniPayApi.action'
            body = alipay.c2b(request_obj)
        elif method == 'quickpay':
            body = alipay.quick_pay(request_obj)
            url = alipay.url + '/trade/noCardPayApi.action'
        elif method == 'query':
            url = alipay.url + '/trade/queryOrder.action'
            body = alipay.query(request_obj)
        elif method == 'gateway':
            url = alipay.url + '/gateway/gateway_init.action'
            body = alipay.gateway(request_obj)
        else:
            return {
                "respcode": "1",
                "payinfo": "未知渠道",
            }
        logger.info(body)
        if method in ("gateway",):
            return self.write({
                "orderstate": "0",
                "payinfo": template.format(**body, url=url)
            })
        body = urllib.parse.urlencode(body)
        # body = "&".join(["%s=%s" % (k, v) for k, v in body.items()])
        response = await http_client.fetch(url, body=body, method='POST', raise_error=False,
                                           request_timeout=59)
        logger.info(response.body.decode())

        if response.code != 200:
            logger.info(response.code)
            if method == 'query':
                orderstate = '8'
            else:
                orderstate = '1'
            return self.write({
                "respcode": orderstate,
                "payorderid": ""
            })

        # logger.info(json.loads(response.body.decode('utf-8'))['resp_msg'])
        qrcode = ""
        payorderid = ""
        message = ""
        if response.body.startswith(b'<'):
            orderstate = '0'
            payinfo = response.body.decode()
            # payinfo = re.findall('href="(.*)"', response.body.decode())
            # if qrcode:
            #     # qrcode = urllib.parse.parse_qsl(urllib.parse.urlparse(qrcode[0]).query)[0][1]
            #     qrcode = qrcode[0]
            # payinfo = ''
        else:
            data_obj = json.loads(response.body.decode())
            data_obj = {k[3:]: v for k, v in data_obj.items()}
            if data_obj['Code'] == '100':
                # status = str(data_obj['Status'])
                if method == 'query':
                    if data_obj['Status'] == '100':
                        orderstate = '0'
                    elif data_obj['Status'] == '102':
                        orderstate = '8'
                    elif data_obj['Status'] in ('103', '101'):
                        orderstate = '1'
                    else:
                        orderstate = '8'
                else:
                    orderstate = '0'
            else:
                if method == 'query':
                    orderstate = '8'
                else:
                    orderstate = '1'
            payinfo = data_obj.get('Result') or data_obj.get("Data")
            qrcode = data_obj.get('Result', '')
            payorderid = data_obj.get("TrxNo")
            message = data_obj.get('CodeMsg')

        result = {
            "respcode": orderstate,
            'orderstate': orderstate,
            'payorderno': payorderid,
            "payorderid": payorderid,
            "payinfo": payinfo,
            "qrcode": qrcode,
            'message': message,
        }
        logger.info(result)
        return self.write(json.dumps(result))


class SettleHandler(tornado.web.RequestHandler):
    async def post(self, method):
        http_client = AsyncHTTPClient()
        logger.info(self.request.body)
        request_obj = json.loads(self.request.body.decode('utf-8'))
        merno = request_obj['CFGPARA']['MERNO']
        appkey = request_obj['CFGPARA']['KEY']
        alipay = self.application.alipay
        alipay.merno = merno
        alipay.appkey = appkey
        if 'OPMONEY' in request_obj:
            request_obj['amount'] = int(request_obj['amount']) / 100
            request_obj['amount'] = '%.2f' % request_obj['amount']
        if method == 'settle':
            body = alipay.settle(request_obj)
            url = alipay.url + '/payment/pay/singlePay'
        elif method == 'query':
            body = alipay.query_sett(request_obj)
            url = alipay.url + '/payment/pay/singlePayQuery'
        else:
            return {
                "respcode": "1",
                "payinfo": "未知渠道",
            }
        logger.info(body)
        body = json.dumps(body)
        response = await http_client.fetch(url, body=body, method='POST', raise_error=False, request_timeout=59,
                                           headers={'Content-Type': 'application/json'})
        logger.info(response.body.decode())
        if response.code != 200:
            logger.info(response.code)
            if method == 'query':
                orderstate = '9'
            else:
                orderstate = '1'
            return self.write({
                "orderstate": orderstate,
                "payorderid": ""
            })
        payorderno = ""
        message = ""
        # 签名验证
        data_obj = json.loads(response.body.decode())
        s = "{statusCode}{message}{errorCode}{userNo}{merchantOrderNo}{key}".format(**data_obj, **data_obj['data'], key=appkey)
        sign = hashlib.md5(s.encode()).hexdigest()
        if sign != data_obj['data']['hmac']:
            return self.write({
                "orderstate": '9',
                "payorderno": payorderno,
                "message": message
            })
        if data_obj['statusCode'] == 2001:
            if method == 'query':
                data = data_obj['data']
                status = str(data['status'])
                message = data['errorDesc']
                payorderno = data.get('platformSerialNo')
                if status == '205':
                    orderstate = '0'
                elif status in ('204', '208', '214',):
                    orderstate = '1'
                else:
                    orderstate = '9'
            else:
                orderstate = '0'

        elif data_obj['statusCode'] == 2002:
            orderstate = '1'
            data = data_obj['data']
            message = data['errorDesc']
        else:
            orderstate = '9'

        return self.write({
            "orderstate": orderstate,
            "payorderno": payorderno,
            "message": message
        })


class SettNotiHandler(PayHandler):
    @gen.coroutine
    def post(self):
        logger.info(self.request.body)
        data = json.loads(self.request.body.decode())
        appkey = self.application.alipay.appkey
        s = '{status}{errorCode}{errorCodeDesc}{userNo}{merchantOrderNo}{platformSerialNo}{receiverAccountNoEnc}{receiverNameEnc}{paidAmount:.2f}{fee:.2f}{key}'.format( **data, key=appkey)
        sign = hashlib.md5(s).hexdigest()
        if sign != s['hmac']:
            return self.write("")
        if data['status'] == 205:
            orderstate = '0'
        elif data['status'] in (201, 202, 203):
            orderstate = '9'
        elif data['status'] in (204, 208, 214):
            orderstate = '1'
        else:
            orderstate = '9'

        self.write({
            "statusCode": "2001",
            "message": "成功"
        })
        self.finish()

        outer_orderno = data.get("merchantOrderNo")
        amount = int(data['paidAmount'] * 100)
        message = data.get('errorCodeDesc', 'none')
        logger.info("通知|流水号%s|支付结果%s", outer_orderno, orderstate)
        proc_req = 'PAY_UNIONPAY_ENTRUST_NOTIFY'
        proc_res = 'PAY_UNIONPAY_ENTRUST_NOTIFY_RE'
        func_code = '200106'
        logger.info(proc_req)
        input_json = {
            "DEPTID": "800001",
            "OPERID": "9527",
            "FUNCTYPE": "NOTI",
            "FUNCCODE": func_code,
            "OPERPASS": "f15dbcb3154d37e4a183a350e4e8a15dde16a175",
            "REQINFO": {
                'OUTERORDERNO': outer_orderno,
                'PAYORDERNO': outer_orderno,
                "ORDERSTATE": orderstate,
                "OPMONEY": str(amount),
                "PAYINFO": message,
            }
        }
        # 发送到核心程序
        logger.info(input_json)
        result = yield from self.pay_core(proc_req, input_json)
        logger.info(result)
        notifyurl = result.get("NOTIFYURL", "")
        logger.info(notifyurl)

        if notifyurl:
            merchantid = result.get("MERCHANTID")
            res = yield self.merchant_exit(merchantid=merchantid)
            for i in range(5):
                notify_data = {
                    "ordernumber": result["MERCHANTORDERNO"],
                    "respcode": result["ORDERSTATE"],
                    "merchantid": merchantid,
                    # "amount": data["amount"],
                    "businesstime": result["RESPTIME"]
                }

                request_obj = self.niti_data(notify_data, result)
                rsp = yield http_result(notifyurl, request_obj, model='urlencode')
                logger.info(rsp.body)
                if rsp.code == 200 and rsp.body in (b'SUCCESS', b'success'):
                    logger.info("%s|%s收到下游成功应答|订单号%s", i, outer_orderno, result["MERCHANTORDERNO"])
                    noti = yield from self.pay_core(proc_res, {
                        "REQINFO": {"MERCHANTID": merchantid, "MERCHANTORDERNO": result["MERCHANTORDERNO"]}})
                    logger.debug(noti)
                    break
                else:
                    logger.info("%s|%s未收到下游成功应答|订单号%s", i, outer_orderno, result["MERCHANTORDERNO"])
                    yield from asyncio.sleep(10 * (i + 1))


#无卡通知
class NotiHandler(PayHandler):
    def post(self):
        logger.info(self.request.body)

        self.write("success")
        self.finish()

    @gen.coroutine
    def get(self):
        logger.info(self.request.body)
        logger.info(self.request.arguments)
        self.write('success')
        self.finish()

        # logger.info("通知|流水号%s|支付结果%s", self.get_argument("orderNo"), self.get_argument("orderStatus"))
        proc_req = 'PAY_UNIONPAY_QUICK_NOTIFY'
        proc_res = 'PAY_UNIONPAY_QUICK_NOTIFY_RES'
        outer_orderno = self.get_argument("r2_OrderNo", "")
        payorderno = outer_orderno
        # orderstate = ""
        message = self.get_argument("r7_TrxNo", "")
        func_code = '200101'
        status = self.get_argument('r6_Status', default="")
        if status == '100':
            orderstate = '0'
        elif status == '101':
            orderstate = '1'
        else:
            orderstate = '9'

        input_json = {
            "DEPTID": "800001",
            "OPERID": "9527",
            "FUNCTYPE": "NOTI",
            "FUNCCODE": func_code,
            "OPERPASS": "f15dbcb3154d37e4a183a350e4e8a15dde16a175",
            "REQINFO": {
                'OUTERORDERNO': outer_orderno,
                'PAYORDERNO': payorderno,
                "ORDERSTATE": orderstate,
                "OPMONEY": "",
                "PAYINFO": message,
            }
        }
        # 发送到核心程序
        logger.info(input_json)
        result = yield from self.pay_core(proc_req, input_json)
        # logger.info(result)
        notifyurl = result.get("NOTIFYURL", "")
        # logger.info(notifyurl)
        logger.info("通知|流水号%s|支付结果%s|%s", result.get("MERCHANTORDERNO"), orderstate, message)
        if notifyurl:
            merchantid = result.get("MERCHANTID")
            res = yield self.merchant_exit(merchantid=merchantid)

            for i in range(5):
                notify_data = {
                    "ordernumber": result["MERCHANTORDERNO"],
                    "respcode": result["ORDERSTATE"],
                    "merchantid": merchantid,
                    # "amount": data["amount"],
                    "payinfo": message,
                    "businesstime": result["RESPTIME"]
                }

                request_obj = self.niti_data(notify_data, result)
                rsp = yield http_result(notifyurl, request_obj, model='urlencode')
                logger.info(rsp.body)
                if rsp.code == 200 and rsp.body in (b'SUCCESS', b'success'):
                    logger.info("%s|%s收到下游成功应答|订单号%s", i, payorderno, result["MERCHANTORDERNO"])
                    noti = yield from self.pay_core(proc_res, {
                        "REQINFO": {"MERCHANTID": merchantid, "MERCHANTORDERNO": result["MERCHANTORDERNO"]}})
                    logger.debug(noti)
                    break
                else:
                    logger.info("%s|%s未收到下游成功应答|订单号%s", i, payorderno, result["MERCHANTORDERNO"])
                    yield from asyncio.sleep(10 * (i + 1))


class AliNotiHandler(PayHandler):
    def get(self):
        logger.info(self.request.arguments)
        self.write('SUCCESS')

    @gen.coroutine
    def post(self):
        logger.info(self.request.body)
        # data = json.loads(self.request.body.decode("utf-8"), parse_float=str)
        self.write("success")
        self.finish()

        outer_orderno = self.get_argument("r2_OrderNo", "")
        payorderno = outer_orderno
        amount = "%d" % (float(self.get_argument('r3_Amount', 0)) * 100)
        message = self.get_argument("r7_TrxNo", "")
        status = self.get_argument('r6_Status', default="")
        if status == '100':
            orderstate = '0'
        elif status == '101':
            orderstate = '1'
        else:
            orderstate = '9'
        logger.info("通知|流水号%s|支付结果%s", outer_orderno, orderstate)

        input_json = {
            "DEPTID": "800001",
            "OPERID": "9527",
            "FUNCTYPE": "NOTI",
            "FUNCCODE": "100201",
            "OPERPASS": "f15dbcb3154d37e4a183a350e4e8a15dde16a175",
            "REQINFO": {
                'OUTERORDERNO': outer_orderno,
                "ORDERSTATE": orderstate,
                "OPMONEY": amount,
                "NOTIFYINFO": message,
            }
        }
        # 发送到核心程序
        result = yield from self.pay_core("PAY_ALIPAY_NOTIFY", input_json)
        logger.info(result)
        notifyurl = result.get("NOTIFIYURL", "")

        if notifyurl:
            merchantid = result.get("MERCHANTID")
            res = yield self.merchant_exit(merchantid=merchantid)
            for i in range(5):
                notify_data = {
                    "ordernumber": result["MERCHANTORDERNO"],
                    "respcode": result["ORDERSTATE"],
                    "merchantid": merchantid,
                    "amount": result["OPMONEY"],
                    "businesstime": result["RESPTIME"]
                }

                request_obj = self.niti_data(notify_data, result)
                rsp = yield http_result(notifyurl, request_obj, model='urlencode')
                logger.info(rsp.body)
                # content = rsp.body.upper()
                if rsp.code == 200 and rsp.body in (b'SUCCESS', b'success'):
                    logger.info("%s|%s收到下游成功应答|订单号%s", i, outer_orderno, result["MERCHANTORDERNO"])
                    noti = yield from self.pay_core("PAY_ALIPAY_NOTIFY_RES", {
                        "REQINFO": {"MERCHANTID": merchantid, "MERCHANTORDERNO": result["MERCHANTORDERNO"]}})
                    logger.debug(noti)
                    break
                else:
                    logger.info("%s|%s未收到下游成功应答|订单号%s", i, outer_orderno, result["MERCHANTORDERNO"])
                    yield from asyncio.sleep(10 * (i + 1))


if __name__ == "__main__":

    application = Application([
        (r"/huiju/(\w+)", AlipayHandler),
        (r"/huijusettle/(\w+)", SettleHandler),
        (r"/noti/huiju", NotiHandler),
        (r"/noti/huiju_sett", SettNotiHandler),
        (r"/noti/huiju_qr", AliNotiHandler)
    ], debug=True)

    alipay = AliPay("https://www.joinpay.com", "", "")
    application.alipay = alipay
    port = '10027'
    loop = asyncio.get_event_loop()
    application.listen(port, address='127.0.0.1')
    logger.info("huiju channel staring")
    application.init_with_loop(loop)
    loop.run_forever()
